import fisheye_consts
import math
import time
import os
from data_packet import DataPacket
from route_update_packet import RouteUpdatePacket

# A singleton FisheyeManager
class FisheyeManager:
    instance = {} 
    
    def __init__(self, location_manager=None, traffic_manager=None): 
        if self.__class__ not in FisheyeManager.instance:
            FisheyeManager.instance[self.__class__] = FisheyeManager.__InnerSingeton(location_manager, traffic_manager)
        else :
            pass
            #print "Warning: trying to create another singleton!"

    def __getattr__(self, name):
        return getattr(self.instance[self.__class__], name)

    def __setattr__(self, name, value):
        return setattr(self.instance[self.__class__], name, value)
    
    # The real Singleton class (inner class)
    class __InnerSingeton:
        def __init__(self, location_manager, traffic_manager):
            """
            Constructor.
            """
            self.DIRNAME = ""


            self._location_manager = location_manager
            self._traffic_manager = traffic_manager
            
            # Initialize all nodes
            self._nodes_by_id = {}
            self._max_link_distance = fisheye_consts.FISHEYE_MAX_LINK_DISTANCE
            self._current_step = 0
            
        def initialize_nodes(self):
            """
            Initializes the nodes.
            """

            # Get the locations in the first (0) tick.
            locations = self._location_manager.get_location_updates_by_tick(self._current_step)
            number_of_nodes = self._location_manager.get_number_of_nodes()
            
            # Create the nodes
            for i in xrange(number_of_nodes):
                import node
                self._nodes_by_id[i] =  node.Node(i, locations[i], self)
            
            # Initialize the nodes
            for i in xrange(number_of_nodes):
                self._nodes_by_id[i].initialize_node()

        def init_packets_info_file(self):
            file(os.path.join(self.DIRNAME, "packets.csv"), "w+")\
                    .write("packet_type, packet_size, src_node, dst_node, num_steps_until_received, num_hops, packed_reached_dst\n")

        def init_steps_info_file(self):
            file(os.path.join(self.DIRNAME, "steps_info.csv"), "w+").write("num_step,numDataPackets,numRoutePackets\n")

        def calc_current_step_info(self):
            """
            Shows the current amount of packets in the network in this step.
            """
            numDataPackets = numRoutePackets = 0
            for node in self._nodes_by_id.values():
                for packet in node._packet_queue.get_packets():
                    if isinstance(packet, DataPacket):
                        numDataPackets += 1
                    elif isinstance(packet, RouteUpdatePacket):
                        numRoutePackets += 1
                    else:
                        pass

            file(os.path.join(self.DIRNAME,"steps_info.csv"), "a+").write("T%d,%d,%d\n" % (self._current_step, numDataPackets, numRoutePackets))

        def write_packet_info(self, packet):
            f = file(os.path.join(self.DIRNAME, "packets.csv"), "a+")
            if isinstance(packet, DataPacket):
                packet_type = "DATA"
            else:
                packet_type = "ROUTE_UPDATE"

            if packet.packet_received == False:
                pass
                #packet.num_hops = -1
            f.write("%s, %d, %d, %d, %d, %d, %d, %d\n" %
                    (packet_type,
                     packet._packet_size,
                     packet._src_node_id,
                     packet._dst_node_id,
                     packet.num_steps_until_received,
                     packet.num_hops,
                     packet.packet_received,
                     packet.mark_sent))
            f.close()

        def calc_packets_latency(self):
            for packet in self._traffic_manager.get_all_packets():
                self.write_packet_info(packet)

        def run_one_step(self):
            """
            Advances one step in the simulation.
            """
            self._update_locations()
            self._update_traffic()

            for node in self._nodes_by_id.values():
                node.set_packets_to_queue()

            self.calc_current_step_info()

            for node in self._nodes_by_id.values():
                node.run_one_step()


            self._current_step += 1
                
        def _update_locations(self):
            """
            Updates the location of all the nodes, according to the current tick/step.
            """
            locations = self._location_manager.get_location_updates_by_tick(self._current_step)
            
            for node_id in locations.keys():
                self._nodes_by_id[node_id].update_location(locations[node_id])
                
        def _update_traffic(self):
            """
            Sends all the new packets, according to the current tick/step.
            """
            print "Updating Trffic:"
            packets = self._traffic_manager.get_packets_by_tick(self._current_step)
            print "Updated Packets: %d %s" % (len(packets), packets)
            for packet in packets:
                print "Hello:", packet
                self._nodes_by_id[packet.get_source()].add_packet_to_queue(packet)
           
        def get_global_step(self):
            """
            Returns the global step.
            """
            return self._current_step
            
        def get_scopes_and_intervals(self):
            """
            Returns the list of scopes (each scope is a distance) and their broadcast intervals.
            """
            return fisheye_consts.FISHEYE_SCOPES_AND_INTERVALS
            
        def get_update_interval_by_distance(self, distance):
            """
            Returns the interval for sending a route update using the distance.
            """
            distances = fisheye_consts.FISHEYE_SCOPES_AND_INTERVALS.keys()[:]
            distances.sort()
            
            for i in range(len(distances)):
                if distances[i] >= distance:
                    return fisheye_consts.FISHEYE_SCOPES_AND_INTERVALS[distances[i]]
            return fisheye_consts.FISHEYE_SCOPES_AND_INTERVALS[fisheye_consts.FISHEYE_INFINITE_DISTANCE]
            
        def get_global_node_id_list(self):
            """
            Returns a list of the ids of the nodes.
            """
            return self._nodes_by_id.keys()
            
        def get_node_object_by_id(self, node_id):
            """
            Returns a Node object by id.
            """
            return self._nodes_by_id[node_id]
                   
        def get_neighbor_list(self, src_node):
            """
            Returns the list of immediate neighbors of a source node.
			src_node is the object itself.
            """
            neighbor_list = []
            
            for node_id in self._nodes_by_id.keys():
				#Weight is 1 if there is link, INF otherwise.
                current_weight = self.get_weight(src_node, self._nodes_by_id[node_id])
                
                # If there is such an edge, then the distance is not infinite
                if current_weight != fisheye_consts.FISHEYE_INFINITE_DISTANCE:
                    neighbor_list.append(node_id)
            
            return neighbor_list
            
        def get_distance(self, src_node_id, dst_node_id):
            """
            Returns the mathematical distance between the src node and the dst node.
            """
            src_location = self._nodes_by_id[src_node_id].get_location()
            dst_location = self._nodes_by_id[dst_node_id].get_location()
            
            return math.sqrt((src_location[0]-dst_location[0])**2  + \
                            (src_location[1]-dst_location[1])**2)
                            
        def get_number_of_hops(self, src_node_id, dst_node_id):
            """
            Returns the distance (number of hops) between the src_node and the dst_node based on the distance table.
            """
            return self._nodes_by_id[src_node_id].get_number_of_hops_to_node(dst_node_id)
            
        def get_weight(self, src_node, dst_node):
            """
            Returns the weight of the edge (src_node, dst_node).
            """
            current_distance =  self.get_distance(src_node.get_node_id(), dst_node.get_node_id())
            if current_distance <= self._max_link_distance:
                return 1
            else:
                return fisheye_consts.FISHEYE_INFINITE_DISTANCE
                       
        def get_weight_by_id(self, src_node_id, dst_node_id):
            """
            Returns the weight of the edge (src_node, dst_node), using their id.
            """
            return self.get_weight(self._nodes_by_id[src_node_id], self._nodes_by_id[dst_node_id])
            
